/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.sunspotworld;

import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import com.sun.spot.peripheral.TimeoutException;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ISwitch;
import com.sun.spot.sensorboard.peripheral.ISwitchListener;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.sensorboard.peripheral.LEDColor;
import com.sun.spot.util.IEEEAddress;
import com.sun.squawk.util.StringTokenizer;
import java.io.IOException;
import java.util.Random;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.Datagram;
import javax.microedition.io.DatagramConnection;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

/**
 *
 * @author seb
 */
public class Main extends MIDlet implements ISwitchListener, Protocol, ApplicationStates{

    private int currentState;
    private RadiogramConnection broadcastReceiveConnection;
    private DatagramConnection broadcastSendConnection;
    private long sendInviteTime;
        
    private ITriColorLED[] leds;
    private ISwitch leftSwitch;
    private ISwitch rightSwitch;

    //DEBUG
    private final boolean DEBUG = true;
    //!DEBUG

    private Vector connections;
    private Vector idleConnections;
    private Vector usedPorts;
    private Game game;
    private final int MIN_CHANNEL = 43;
    private final int GENERATED_PORTS = 10;


    protected void startApp() throws MIDletStateChangeException {
    }

    public Main(){
        sendInviteTime = 0;
        currentState = CONNECTING;
        connections = new Vector();
        idleConnections = new Vector();
        game = new Game(this);
        usedPorts = new Vector();
        leds = EDemoBoard.getInstance().getLEDs();
        leftSwitch = EDemoBoard.getInstance().getSwitches()[0];
        rightSwitch = EDemoBoard.getInstance().getSwitches()[1];
        leftSwitch.addISwitchListener(this);
        rightSwitch.addISwitchListener(this);
        
        initConnecting();
        start();             
    }

    public void resetConnections() {
        for(int i = 0; i<idleConnections.size(); ++i){
            Connection con = (Connection) idleConnections.elementAt(i);
            connections.addElement(con);
        }

        idleConnections.removeAllElements();
    }

    /**
     * the one and only loop in the application
     */
    private void start() {
        while(true){
            switch(currentState) {
                case CONNECTING:
                    connecting();
                    break;
                case PLAYING:
                    playing();
                    listenToHost();
                    break;
            }
        }
    }

    /**
     * to open the broadcastconnections needed foor the handshakes and the hostmessages
     */
    private void initConnecting() {
        try {
            broadcastSendConnection = (RadiogramConnection) Connector.open("radiogram://broadcast:37");
            broadcastReceiveConnection = (RadiogramConnection) Connector.open("radiogram://:37");
        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }

    /**
     * sends invites for the handshake and catches handshake messages
     */
    private void connecting() {
             /*
             * send broadcast
            **/
            String message = INVITE +" ";
            int[] ports = generateAvailablePortNumbers(GENERATED_PORTS);

            for(int i = 0; i< GENERATED_PORTS; i++){
                message += ports[i]+" ";
            }

            if((sendInviteTime + 1000) < System.currentTimeMillis()) {
                sendInviteTime = System.currentTimeMillis();
                sendBroadcastMessage(message);
                System.out.println("invite send");
            }
            
            
            /*
             * receive broadcast
            **/
            try {
                Datagram datagram = broadcastReceiveConnection.newDatagram(broadcastReceiveConnection.getMaximumLength());
                if(broadcastReceiveConnection.packetsAvailable()) {
                    broadcastReceiveConnection.receive(datagram);
                    receiveBroadcastMessage(datagram);
                }
            } catch(IOException ioe) {
                ioe.printStackTrace();
            } catch (NullPointerException npe) {
                npe.printStackTrace();
            }

            display();
    }

    /**
     * catches the messages coming from the hostapp
     */
    private void listenToHost() {
        try{
            Datagram datagram = broadcastReceiveConnection.newDatagram(broadcastReceiveConnection.getMaximumLength());
            if(broadcastReceiveConnection.packetsAvailable()) {
                broadcastReceiveConnection.receive(datagram);
                receiveHostMessage(datagram);
            }
        } catch(IOException ioe) {
            ioe.printStackTrace();
        }
    }

    /**
     * catches the messages comming from connections and sends them to the game
     * also calls the update-method from the game
     */
    private void playing() {
        String message = "";
    
            try {
                //check for messages from active players
                for(int i=0; i<connections.size(); i++) {
                    Connection con = (Connection)connections.elementAt(i);
                    if(con.hasMessage()) {
                        message = con.readUTF();
                        game.receiveMessage(message, con.getAddress());
                    }
                }

                //check for messages from idle players
                for(int i=0; i<idleConnections.size(); i++) {
                    Connection con = (Connection)idleConnections.elementAt(i);
                    if(con.hasMessage()) {
                        message = con.readUTF();
                        game.receiveMessage(message, con.getAddress());
                    }
                }
            } catch(IOException ioe) {
                        ioe.printStackTrace();
            }
            game.update();//to check for the potatoelifetime constantly etc
    }

    /**
     * to display the number of connections made in the connection-fase
     */
    private void display() {
        if(connections.isEmpty()) {
            leds[0].setRGB(10, 0, 0);
            leds[7].setRGB(10, 0, 0);
        }
        else {
            leds[0].setRGB(0, 0, 10);
            leds[7].setRGB(0, 0, 10);

            for (int i = 1; i <= connections.size(); i++) {
                leds[i].setRGB(0, 10, 0);
                leds[i].setOn();
            }
        }

        leds[0].setOn();
        leds[7].setOn();
    }

    private void addConnection(long address, int port) throws IOException{

       if(!containsAddress(address)){
           connections.addElement((Object) new Connection(address, port));
       }else{
           System.err.println("Error @ Main.java:addConnection() - Connection is already in the vector!");
       }
    }

    private boolean containsAddress(long address) {
        boolean containsAddress = false;

        for(int i = 0; i < connections.size(); ++i) {
            Connection con = (Connection) connections.elementAt(i);
            if(con.getAddress() == address) {
               containsAddress = true;
            }
        }

        return containsAddress;
    }

    private void openConnections(){
        for(int i = 0; i < connections.size(); ++i) {
            Connection con = (Connection)connections.elementAt(i);
            con.open(con.getAddress(), con.getPortNumber());
        }
    }
    
    public Connection getConnectionByAddress(long address){

        for(int i=0; i<connections.size(); i++){
            Connection con = (Connection)connections.elementAt(i);

            if(con.getAddress() == address){
                return con;
            }
        }
        
        return null;
    }

    private void sendBroadcastMessage(String message){

       try{
           Datagram dg = broadcastSendConnection.newDatagram(broadcastSendConnection.getMaximumLength());
            dg.writeUTF(message);
            broadcastSendConnection.send(dg);
       }catch (IOException ex){
           System.err.println("Error @ Main.java:sendBroadcastMessage - Could not send datagram");
           ex.printStackTrace();
       }

    }

    public void sendConnectionMessage(String message, long address){

        Connection con = getConnectionByAddress(address);

        if(con != null){
            try {
                con.writeUTF(message);
                con.flush();
            } catch (IOException ex) {
                System.err.println("Error @ Main.java:sendConnectionMessage - Could not write to connection!");
                ex.printStackTrace();
            }
        } else {
            System.err.println("Error @ Main.java:sendConnectionMessage - Connection is null!");
        }
    }

    /**
     *
     * to send a single message to all connections(players)
     */
    public void massMessage(String message){
        
        try {
            for(int i=0; i<connections.size(); i++){
                Connection con = (Connection)connections.elementAt(i);
                System.out.println("MassMessage:" + con.toString() + " " + con.getAddress() + " " + con.getPortNumber());
                con.writeUTF(message);
                con.flush();
            }

            for(int i=0; i<idleConnections.size(); i++){
                Connection con = (Connection)idleConnections.elementAt(i);
                System.out.println("MassMessage:" + con.toString() + " " + con.getAddress() + " " + con.getPortNumber());
                con.writeUTF(message);
                con.flush();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    /**
     *
     * to reply to a handshake message or a hostapp-message
     */
    private void replyWithDatagram(String message, Datagram addressSourceDG){
        Datagram dgreply;
        
        try{
           dgreply = broadcastReceiveConnection.newDatagram(broadcastReceiveConnection.getMaximumLength());
           dgreply.reset();                   
           dgreply.setAddress(addressSourceDG.getAddress());
           dgreply.writeUTF(message);              
           broadcastReceiveConnection.send(dgreply);          
        } catch (IOException ex) {
           System.out.println("----------Retrying...");
           replyWithDatagram(message,addressSourceDG);
        }
    }


    /**
     *
     * handles the hostmessages
     */
    private void receiveHostMessage(Datagram datagram) {
        String message = "";

        try{
            message = datagram.readUTF();
            String messageTokens[] = separateStrings(message);

            switch(Integer.parseInt(messageTokens[0])) {
                case GET_SCORE:
                    replyWithDatagram(RECEIVE_SCORE + " " + game.getScore(), datagram);
                    System.out.println("Message to host send");
                    break;
                case GET_STATE:
                    replyWithDatagram(RECEIVE_STATE + " " + game.getCurrentState(), datagram);
                    break;
                case SET_GAMETIME:
                    long gameTime = Long.parseLong(messageTokens[1]);
                    game.setGameTime(gameTime);
                    break;
            }
        } catch(IOException ioe) {

        } catch (NumberFormatException nfe) {
            
        }
    }

    /**
     *
     * handles the received handshakemessages
     */
     private void receiveBroadcastMessage(Datagram datagram){
         String message;
         int port = -1;
         String otherAddress =  datagram.getAddress();
         long otherAddressAsNumber = IEEEAddress.toLong(otherAddress);
         boolean accepted = false;

        try {
            message = datagram.readUTF();
            String messageTokens[] = separateStrings(message);

//            System.out.println(messageTokens[0]);

            if(!(containsAddress(IEEEAddress.toLong(datagram.getAddress())))){

                switch(Integer.parseInt(messageTokens[0])){



//                    ////////////////////////////////////
//                    ///
//                    ///  HOST
//                    ///
//                    ////////////////////////////////////
//                    case GET_SCORE:
//                        replyWithDatagram(RECEIVE_SCORE + " " + game.getScore(), datagram);
//                        System.out.println("Message to host send");
//                        break;
//                    case GET_STATE:
//                        replyWithDatagram(RECEIVE_STATE + " " + game.getCurrentState(), datagram);
//                        break;
                    case SET_GAMETIME:
                        replyWithDatagram(NOTPLAYING + "", datagram);
                        break;
                    ////////////////////////////////////
                    ///
                    ///  RECEIVER
                    ///
                    ////////////////////////////////////
                    // Got invite to the game
                    case(INVITE):
                        System.out.println("got invite");

//                        if(!isBusyHandshakingWith(otherAddressAsNumber)) {
//                            handshakes.addElement(new Handshake(otherAddressAsNumber, System.currentTimeMillis()));
//                        }
                        
                        int ports[] = new int[GENERATED_PORTS];

                        for(int i = 1; i<=GENERATED_PORTS; i++){
                            ports[i-1]=Integer.parseInt(messageTokens[i]);
                        }

                        port = selectPortFrom(ports);
                        replyWithDatagram(ACCEPTINVITE + " " + otherAddressAsNumber + " " + port, datagram);
                        break;

                    ////////////////////////////////////
                    ///
                    ///  SENDER
                    ///
                    ////////////////////////////////////
                    // Got confirmation of the invitation
                    case(ACCEPTINVITE):
                        long ownAddress = Long.parseLong(messageTokens[1]);

                        if(ownAddress > otherAddressAsNumber) {
                            System.out.println("accepting");
                            port = Integer.valueOf(messageTokens[2]).intValue();

                            if(!usedPorts.contains( new Integer(port) ) ){

                                usedPorts.addElement(new Integer(port));
                                if(DEBUG){
                                    System.out.println("--> Send confirm!");
                                }
                                replyWithDatagram(CONFIRMINVITE +" " + port, datagram);
                                accepted = true;
                            } // kill connection
                                else{
                                    // somebody uses the same port number! Cancel and begin again...
                                    replyWithDatagram(CANCEL_CONNECTION+"", datagram);
                                    accepted=false;
                                }
                        }else{
                            System.out.println("Waiting for other side to proceed");
                        }

                        break;
                                
                    ////////////////////////////////////
                    ///
                    ///  RECEIVER
                    ///
                    ////////////////////////////////////
                    // Got confirmation of the confirmation -> Connection accepted!
                    case(CONFIRMINVITE):
                        if(DEBUG){
                            System.out.println("--> Got confirm!");
                        }
                                
                        port = Integer.valueOf(messageTokens[1]).intValue();
                        usedPorts.addElement(new Integer(port));
                        accepted = true;
                        break;

                    ////////////////////////////////////
                    ///
                    ///  RECEIVER
                    ///
                    ////////////////////////////////////
                    // Connection canceled
                    case(CANCEL_CONNECTION):
                        if(DEBUG){
                            System.out.println("X--X Connection killed by server... reconnecting");
                        }
                        accepted = false;
                        break;
                }
                // Handshake okay? Save the connection
                if(accepted){
                    if(DEBUG){
                        System.out.println("<--> Connection accepted! " + otherAddressAsNumber +":"+port);
                    }
                                
                     addConnection(otherAddressAsNumber, port);                
                }
            }

        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (NumberFormatException nfe){
            nfe.printStackTrace();
        }
     }

     /**
      *
      * to set a active player to idle when he's dead in the game
      */
     public void setConnectionToIdle(long address) {
         Connection con = getConnectionByAddress(address);
         if(con != null){
             connections.removeElement(con);
             idleConnections.addElement(con);
             System.out.println(address + " :idleConnection");
         }
     }

     /**
      *
      * called when sending a handshake-invitation to let the receiver choose one of the 10 available portnumbers
      */
      private int[] generateAvailablePortNumbers(int total){
        int[] ports = new int[total];
        Vector used = new Vector();

        // Note in Java's &&, right hand expression is not evaluated unless left is true.

        for(int i=0; i<connections.size(); i++){
            Connection con = (Connection) connections.elementAt(i);
           
            used.addElement(new Integer( con.getPortNumber()));
        }
        //Start at the lowest channel, and search upwards till an available one is found.

        int c = MIN_CHANNEL;
        int index = 0;
        while(index < total){
            if (! used.contains(new Integer(c))){
                ports[index] = c;
                index = index + 1;
            }
            c = c + 1;
        }
        return ports;
    }

      /**
       *
       * called when the spot is the receiver of a handshake to select 1 port from 10
       */
    private int selectPortFrom(int[] array) {

        Vector pieces = new Vector();
        for(int i = 0; i<array.length; i++){
            pieces.addElement(array[i]+"");
        }

        String port;

        for(int i=0; i<connections.size(); i++){
            Connection con = (Connection) connections.elementAt(i);

            if(con != null) {
                port = "" +  con.getPortNumber();

                if(pieces.contains(port)){
                    pieces.removeElement(port);
                }
            }
        }
        // return a random port
        Random rand = new Random();
        return Integer.parseInt( (String)( pieces.elementAt( rand.nextInt( pieces.size()-1 ) ) ) );
    }

     public Vector getConnections(){
         return connections;
     }

     /**
      *
      * to cut a received message up in seperate pieces
      */
    public String[] separateStrings(String message) {
        StringTokenizer stk = new StringTokenizer(message, " ");
        String [] result = new String[stk.countTokens()];
        for (int i = 0; stk.hasMoreTokens(); i++) {
            result[i] = stk.nextToken();
            }
        return result;
    }

    protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
    }

    protected void pauseApp() {
    }

    public void switchPressed(ISwitch sw) {
        if(sw == leftSwitch) {
            /*
             * send broadcast
            **/
            String message = INVITE +" ";
            int[] ports = generateAvailablePortNumbers(GENERATED_PORTS);

            for(int i = 0; i< GENERATED_PORTS; i++){
                message += ports[i]+" ";
            }
            sendBroadcastMessage(message);
            System.out.println("invite send");
        } else if(connections.size() > 0) {
            openConnections();
            currentState = PLAYING;
            leftSwitch.removeISwitchListener(this);
            rightSwitch.removeISwitchListener(this);
            game.setup();
        }
    }

    public void switchReleased(ISwitch sw) {
    }
}